SPDX-FileCopyrightText: 2026 Béatrice Versickas & Mark Akoury SPDX-FileCopyrightText: 2026 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import math
import random
import bmesh
import bpy------------------------------------------------------------------------------------CLEAN
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)------------------------------------------------------------------------------------MELODY and CHORDS
creation de vaque orgnaique
def create_organic_wave(
nom_objet, z_location, amplitude, freq, time_offset, octree_depth, scale, threshold
):
taille_grille = 10
subdivisions = 100
organic_blend = 1.0supprimer l’ancienne grille si elle existe
if nom_objet in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[nom_objet], do_unlink=True)créer une nouvelle grille
bpy.ops.mesh.primitive_grid_add(
size=taille_grille,
x_subdivisions=subdivisions,
y_subdivisions=subdivisions,
location=(0, 0, z_location),
)répéter la grille
obj = bpy.context.active_object
obj.name = nom_objetdéformation
mesh = obj.data
bm = bmesh.new()
bm.from_mesh(mesh)modifier les points de la grille
for v in bm.verts:
x = v.co.x
y = v.co.yvagues
wave1 = math.sin((x + y) * freq + time_offset)
wave2 = math.cos((x - y * 1.3) * freq * 0.8 - time_offset)distance au centre pour avoir une sorte d’onde circulaire
distance = math.sqrt(x**2 + y**2)
radial = math.sin(distance * freq * 1.5 - time_offset * 1.2)
v.co.z = amplitude * (wave1 + wave2 + (organic_blend * radial)) / 3.0
bm.to_mesh(mesh)
bm.free()
mesh.update()créer les sortes de pixels avec le modifiers remesh en blocks
remesh_mod = obj.modifiers.new(name="Voxel_Blocks", type="REMESH")
remesh_mod.mode = "BLOCKS"
remesh_mod.octree_depth = octree_depth # niveau de détail
remesh_mod.scale = scale # taille globale du remesh
remesh_mod.threshold = threshold # définit un seuil
bpy.ops.object.shade_flat() # évite de faire un effet lisse car on veut des voxelsGENERER LES VAGUES
Choix d’une fréquence aléatoire qui sera partagée par les deux vagues
freq_partagee = random.uniform(0.25, 0.4) # pour qu'elles s'entremelent
offset_base = random.uniform(0, math.pi * 2) # pour éviter que ce soit symétriquevague 1 = chords
create_organic_wave(
nom_objet="Wave_1",
z_location=4.5,
amplitude=2.5, # force des vagues
freq=freq_partagee, # meme fréquence que la vague 2
time_offset=offset_base,
octree_depth=5, # faible détails pour avoir de grands voxels
scale=0.9, # taille globale
threshold=1.0,
)vague 2 = melody
create_organic_wave(
nom_objet="Wave_2",
z_location=6,
amplitude=4.5,
freq=freq_partagee,
time_offset=offset_base + 1.0, # décalage pour avoir un mouvement diff
octree_depth=8,
scale=0.9,
threshold=1.0,
)------------------------------------------------------------------------------------KICKS
collection
col_name = "GENERATED_KICK"si le collection existe on la recupère sinon on l’a créer
if col_name in bpy.data.collections:
target_col = bpy.data.collections[col_name]
else:
target_col = bpy.data.collections.new(col_name)
bpy.context.scene.collection.children.link(target_col)choix aléatoire des 4 différents kicks
style = random.randint(1, 4)grille
max_height = 10.0
grid_size = 10.0
offset = grid_size / 2 # recentre la grille sur 0,0chaque kick est différent
if style == 1:
step = 3
min_colonnes = 4
elif style == 2:
step = 3
min_colonnes = 6
elif style == 3:
step = 3
min_colonnes = 4
else:
step = 1
min_colonnes = 8creation grille
n = int(grid_size // step) # nombre de cases sur la grille
positions = []on génère tout les points possibles sur la grille
for i in range(n):
for j in range(n):générer tout les points possibles sur la grille
x = (i * step + step / 2) - offset
y = (j * step + step / 2) - offset
positions.append((x, y))selection random des points
nombre_colonnes = random.randint(min_colonnes, len(positions))
positions_choisies = random.sample(positions, nombre_colonnes)creer les cubes
for x, y in positions_choisies:
h = random.uniform(1.0, max_height) # hauteur aléatoire
bpy.ops.mesh.primitive_cube_add(location=(x, y, h / 2)) # le cube
obj = bpy.context.objectchaque kick est diff
if style == 1:
obj.scale = (0.35, 0.35, h / 2)
elif style == 2:
if random.random() < 0.5:
obj.scale = (0.35, 0.35, h / 2)
else:
obj.scale = (0.15, 0.15, h / 2)
elif style == 3 or style == 4:
obj.scale = (0.15, 0.15, h / 2)objet dans la collection
if obj.name not in target_col.objects:
target_col.objects.link(obj)retire des autres collections
for col in obj.users_collection:
if col != target_col:
col.objects.unlink(obj)------------------------------------------------------------------------------------BASS
selected_bass = random.randint(1, 4) # selection random des 4 basses diff que l'on agrille
if selected_bass == 1:grille regulière
step = 1.0
zone_size = 10décalage pour centrer la grille
offset_x = -5
offset_y = -5hauteurs
max_height = 1.5
min_height = 0.8taille
size = 1.0------------------------------------------------------------------------------------basse1
collection basse1
if "basse1" in bpy.data.collections:
basse1 = bpy.data.collections["basse1"]
else:
basse1 = bpy.data.collections.new("basse1")
bpy.context.scene.collection.children.link(basse1)Nombre de cubes par côté (10 / 1.0 = 10)
n = int(zone_size / step)
for i in range(n):
for j in range(n):on calcule le coin de la cellule
corner_x = i * step
corner_y = j * stephauteur aléatoire
h = random.uniform(min_height, max_height) center_x = corner_x + (size / 2)
center_y = corner_y + (size / 2)centre exacte
x = center_x + offset_x
y = center_y + offset_ycube
bpy.ops.mesh.primitive_cube_add(location=(x, y, h / 2))
obj = bpy.context.objectmise à l’échelle
obj.scale = (size / 2, size / 2, h / 2)mettre dans collection basse1
for col in obj.users_collection:
col.objects.unlink(obj)
basse1.objects.link(obj)------------------------------------------------------------------------------------basse2
elif selected_bass == 2:
step = 0.5
zone_size = 10
offset_x = -5
offset_y = -5petits et gros blocs avec des hauteurs diffs
max_small = 1.5
min_small = 0.8
max_big = 3.5
min_big = 2.0tailles
size_small = 0.5
size_big = 1.5collection basse2
if "basse2" in bpy.data.collections:
basse2 = bpy.data.collections["basse2"]
else:
basse2 = bpy.data.collections.new("basse2")
bpy.context.scene.collection.children.link(basse2)probabilité grosse colonne
prob_big = 0.0410 / 0.5 = 20 on boucle exactement 20 fois (de 0 à 19)
n = int(zone_size / step)
for i in range(n):
for j in range(n):calcule le coin de la cellule
corner_x = i * step
corner_y = j * stepchoix aléatoire (gros bloc ou petits)
is_big = random.random() < prob_bigévite que les gros blocs dépassent de la grille
if is_big and (
(corner_x + size_big > zone_size) or (corner_y + size_big > zone_size)
):
is_big = Falsetaille + hauteur selon type
if is_big:
size = size_big
h = random.uniform(min_big, max_big)
else:
size = size_small
h = random.uniform(min_small, max_small)
center_x = corner_x + (size / 2)
center_y = corner_y + (size / 2)
x = center_x + offset_x
y = center_y + offset_y
bpy.ops.mesh.primitive_cube_add(location=(x, y, h / 2))
obj = bpy.context.object
obj.scale = (size / 2, size / 2, h / 2)
for col in obj.users_collection:
col.objects.unlink(obj)
basse2.objects.link(obj)------------------------------------------------------------------------------------basse3
elif selected_bass == 3:
step = 0.5
zone_size = 10
offset_x = -5
offset_y = -5hauteurs
max_height = 1.5
min_height = 0.8taille
size = 0.5collection basse3
if "basse3" in bpy.data.collections:
basse3 = bpy.data.collections["basse3"]
else:
basse3 = bpy.data.collections.new("basse3")
bpy.context.scene.collection.children.link(basse3)nombre de cubes par côté (10 / 0.5 = 20)
n = int(zone_size / step)
for i in range(n):
for j in range(n):
corner_x = i * step
corner_y = j * step
h = random.uniform(min_height, max_height)
center_x = corner_x + (size / 2)
center_y = corner_y + (size / 2)
x = center_x + offset_x
y = center_y + offset_y
bpy.ops.mesh.primitive_cube_add(location=(x, y, h / 2))
obj = bpy.context.object
obj.scale = (size / 2, size / 2, h / 2)
for col in obj.users_collection:
col.objects.unlink(obj)
basse3.objects.link(obj)------------------------------------------------------------------------------------basse4
elif selected_bass == 4:Paramètres
step = 0.5
zone_size = 10
offset_x = -5
offset_y = -5hauteurs
max_small = 3.5
min_small = 1.5
max_big = 2
min_big = 0.8tailles
size_small = 0.5
size_big = 1.5collection basse4
if "basse4" in bpy.data.collections:
basse4 = bpy.data.collections["basse4"]
else:
basse4 = bpy.data.collections.new("basse4")
bpy.context.scene.collection.children.link(basse4)probabilité grosse colonne
prob_big = 0.110 / 0.5 = 20. On boucle exactement 20 fois (de 0 à 19)
n = int(zone_size / step)pour ne pas que les cubes se mettent dans d’autres cubes (savoir les cases deja utilisées)
occupied = set()taille d’un gros bloc en case (1.5 / 0.5 = 3)
span = int(round(size_big / step))
for i in range(n):
for j in range(n):si case déja utilisée
if (i, j) in occupied:
continue
corner_x = i * step
corner_y = j * step
is_big = random.random() < prob_bigverifier limites et coliisions
if is_big:vérifie s’il déborde des bords du 10x10
if (corner_x + size_big > zone_size) or (
corner_y + size_big > zone_size
):
is_big = False
else:vérifie si l’espace 3x3 requis est totalement vide
for dx in range(span):
for dy in range(span):
if (i + dx, j + dy) in occupied:
is_big = False
break
if not is_big:
breaktaille + rappel
if is_big:
size = size_big
h = random.uniform(min_big, max_big)marque toute la zone 3x3 comme occupée
for dx in range(span):
for dy in range(span):
occupied.add((i + dx, j + dy))
else:
size = size_small
h = random.uniform(min_small, max_small)marque juste cette petite case comme occupée
occupied.add((i, j))on trouve le centre PARFAIT en ajoutant la moitié de la taille au coin
center_x = corner_x + (size / 2)
center_y = corner_y + (size / 2)offset pour la position finale
x = center_x + offset_x
y = center_y + offset_y
bpy.ops.mesh.primitive_cube_add(location=(x, y, h / 2))
obj = bpy.context.object
obj.scale = (size / 2, size / 2, h / 2)
for col in obj.users_collection:
col.objects.unlink(obj)
basse4.objects.link(obj)